package com.idanchuang.component.redis.aspect;

import com.idanchuang.component.base.exception.common.ErrorCode;
import com.idanchuang.component.base.exception.core.ExFactory;
import com.idanchuang.component.redis.annotation.RedisLock;
import com.idanchuang.component.redis.helper.BusinessKeyHelper;
import com.idanchuang.component.redis.util.DRedisLock;
import com.zoi7.component.core.base.BaseClass;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * Aspect for methods with {@link RedisLock} annotation.
 *
 * @author yjy
 */
@Aspect
public class RedisLockAspect extends BaseClass {

    private static final Logger log = LoggerFactory.getLogger(RedisLockAspect.class);

    @Pointcut("@annotation(com.idanchuang.component.redis.annotation.RedisLock)")
    public void redisLockAnnotationPointcut() {
    }

    @Around("redisLockAnnotationPointcut()")
    public Object invokeWithRedisLock(ProceedingJoinPoint pjp) throws Throwable {
        Method originMethod = resolveMethod(pjp);
        RedisLock annotation = originMethod.getAnnotation(RedisLock.class);
        if (annotation == null) {
            // Should not go through here.
            throw new IllegalStateException("Wrong state for RedisLock annotation");
        }
        DRedisLock lock = null;
        String lockName = getName(annotation.value(), originMethod);
        lockName += BusinessKeyHelper.getKeyName(pjp, annotation.keys());
        try {
            lock = new DRedisLock(lockName, annotation.lockTimeout(), annotation.autoRenew());
            // 获取锁, 如果被占用则等待, 直到获取到锁, 或则等待超时
            if (lock.tryLock(annotation.tryTimeout(), TimeUnit.MILLISECONDS)) {
                return pjp.proceed();
            } else {
                String msg = "Get redisLock failed, lockName: " + lockName;
                log.warn(msg);
                throw ExFactory.throwWith(ErrorCode.CONFLICT, isBlank(annotation.errMessage()) ? msg : annotation.errMessage());
            }
        } finally {
            // 重点: 释放锁
            if (lock != null) {
                lock.unlock();
            }
        }
    }

    /**
     * 获取lockName前缀
     *
     * @param lockName
     * @param originMethod
     * @return java.lang.String
     * @author sxp
     * @date 2020/7/3 11:06
     */
    private String getName(String lockName, Method originMethod) {
        // if 未指定lockName, 则默认取 类名:方法名
        if (isBlank(lockName)) {
            return originMethod.getDeclaringClass().getSimpleName() + ":" + originMethod.getName();
        } else {
            return lockName;
        }
    }

    private Method resolveMethod(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Class<?> targetClass = joinPoint.getTarget().getClass();

        Method method = getDeclaredMethodFor(targetClass, signature.getName(),
                signature.getMethod().getParameterTypes());
        if (method == null) {
            throw new IllegalStateException("Cannot resolve target method: " + signature.getMethod().getName());
        }
        return method;
    }

    /**
     * Get declared method with provided name and parameterTypes in given class and its super classes.
     * All parameters should be valid.
     *
     * @param clazz          class where the method is located
     * @param name           method name
     * @param parameterTypes method parameter type list
     * @return resolved method, null if not found
     */
    private Method getDeclaredMethodFor(Class<?> clazz, String name, Class<?>... parameterTypes) {
        try {
            return clazz.getDeclaredMethod(name, parameterTypes);
        } catch (NoSuchMethodException e) {
            Class<?> superClass = clazz.getSuperclass();
            if (superClass != null) {
                return getDeclaredMethodFor(superClass, name, parameterTypes);
            }
        }
        return null;
    }

}
